package lol.clann.minecraft.springboot.plugin.bukkit.impl;

import lol.clann.minecraft.springboot.api.annotation.compatibility.CompatibilityChecked;
import lol.clann.minecraft.springboot.api.annotation.condition.ConditionalOnMinecraftVersion;
import lol.clann.minecraft.springboot.api.annotation.condition.MinecraftVersionRange;
import lol.clann.minecraft.springboot.api.bukkit.constant.NBTTypeEnum;
import lol.clann.minecraft.springboot.api.bukkit.utils.NBTUtils;
import lol.clann.minecraft.springboot.api.model.reflect.FieldInvoker;
import lol.clann.minecraft.springboot.bukkit.utils.BukkitUtils;
import lol.clann.minecraft.springboot.bukkit.utils.NMSClasses;
import lol.clann.minecraft.springboot.bukkit.utils.NMSReflectUtils;
import net.minecraft.server.v1_12_R1.NBTBase;
import net.minecraft.server.v1_12_R1.NBTCompressedStreamTools;
import net.minecraft.server.v1_12_R1.NBTTagCompound;
import net.minecraft.server.v1_12_R1.NBTTagList;
import net.minecraft.server.v1_7_R4.NBTTagString;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Service;

import java.io.*;
import java.util.List;
import java.util.Map;
import java.util.StringJoiner;

/**
 * @author pyz
 * @date 2019/5/3 3:10 PM
 */
@Service
@CompatibilityChecked
@ConditionalOnMinecraftVersion(@MinecraftVersionRange(max = 11500))
public class NBTUtilsImpl_all implements NBTUtils {

    private static FieldInvoker type;

    static {
        type = new FieldInvoker(NMSReflectUtils.getField(NMSClasses.getNBTTagList(), "type"));
    }

    @Override
    public void serialize(Object nbtTagCompound, OutputStream out) throws IOException {
        NBTCompressedStreamTools.a((NBTTagCompound) nbtTagCompound, out);
        out.close();
    }

    @Override
    public byte[] serialize(Object nbtTagCompound) throws IOException {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        serialize(nbtTagCompound, out);
        return out.toByteArray();
    }

    @Override
    public Object deserialize(InputStream in) throws IOException {
        try (InputStream in0 = in) {
            return NBTCompressedStreamTools.a(in);
        }
    }

    @Override
    public Object deserialize(byte[] bytes) throws IOException {
        return deserialize(new ByteArrayInputStream(bytes));
    }

    @Override
    public Object fromValue(Object value) {
        if (NBTTypeEnum.isNbt(value)) {
            return value;
        }
        NBTTypeEnum type = NBTTypeEnum.getByValueType(value.getClass());
        Object nbtTag = type.getConstructor().invoke(null);
        type.getField().set(nbtTag, value);
        return nbtTag;
    }

    @Override
    public Object newNBTTagList() {
        return NBTTypeEnum.NBTTagList.getConstructor().invoke(null);
    }

    @Override
    public Object newNBTTagCompound() {
        return NBTTypeEnum.NBTTagCompound.getConstructor().invoke(null);
    }

    @Override
    public Object getValue(Object nbtTag) {
        if (nbtTag == null) {
            return null;
        }
        NBTTypeEnum type = NBTTypeEnum.getByType(nbtTag.getClass());
        return type.getField().get(nbtTag);
    }

    @Override
    public void setValue(Object nbtTag, Object value) {
        NBTTypeEnum type = NBTTypeEnum.getByType(nbtTag.getClass());
        type.getField().set(nbtTag, value);
    }

    @Override
    public Object getValue(Object nbtTagCompound, String key) {
        Object tag = get(nbtTagCompound, key);
        return getValue(tag);
    }

    /**
     * 直接设置值
     *
     * @param nbtTagCompound
     * @param key
     * @param value          可以是值,也可以是nbt
     */
    @Override
    public void setValue(Object nbtTagCompound, String key, Object value) {
        if (value == null) {
            remove(nbtTagCompound, key);
            return;
        } else {
            set(nbtTagCompound, key, fromValue(value));
        }
    }

    @Override
    public Object get(Object nbtTagCompound, String key) {
        if (nbtTagCompound == null) {
            return null;
        }
        Map map = (Map) getValue(nbtTagCompound);
        return map.get(key);
    }

    @Override
    public Object getOrCreateNBTTagCompound(Object nbtTagCompound, String key) {
        Map map = (Map) getValue(nbtTagCompound);
        Object tag = map.get(key);
        if (tag == null) {
            tag = new NBTTagCompound();
            map.put(key, tag);
        }
        return tag;
    }

    @Override
    public Object getOrCreateNBTTagList(Object nbtTagCompound, String key) {
        Map map = (Map) getValue(nbtTagCompound);
        Object tag = map.get(key);
        if (tag == null) {
            tag = new NBTTagList();
            map.put(key, tag);
        }
        return tag;
    }

    @Override
    public void set(Object nbtTagCompound, String key, Object nbtTag) {
        Map map = (Map) getValue(nbtTagCompound);
        map.put(key, nbtTag);
    }

    @Override
    public Object remove(Object nbtTagCompound, String key) {
        NBTBase value = ((NBTTagCompound) nbtTagCompound).get(key);
        if (value != null) {
            ((NBTTagCompound) nbtTagCompound).remove(key);
        }
        return value;
    }

    @Override
    public int size(Object nbtTag) {
        Object value = getValue(nbtTag);
        if (value instanceof Map) {
            return ((Map) value).size();
        } else if (value instanceof List) {
            return ((List) value).size();
        } else {
            throw new UnsupportedOperationException(String.valueOf(nbtTag));
        }
    }

    @Override
    public <T> T deepCopy(T nbtTag) {
        if (nbtTag == null) {
            return null;
        }
        return (T) ((NBTBase) nbtTag).clone();
    }

    /**
     * 将nbt转化成rawMessage需要的josn字串
     *
     * @param nbtTag
     * @return
     */
    @Override
    public String toJsonStringForRawMessage(Object nbtTag) {
        if (NBTTypeEnum.NBTTagCompound.isInstance(nbtTag)) {
            StringJoiner sj = new StringJoiner(",", "{", "}");
            Map<String, Object> tagMap = (Map<String, Object>) getValue(nbtTag);
            tagMap.forEach((k, v) -> {
                sj.add(k + ":" + toJsonStringForRawMessage(v));
            });
            return sj.toString();
        } else if (NBTTypeEnum.NBTTagList.isInstance(nbtTag)) {
            StringJoiner sj = new StringJoiner(",", "[", "]");
            List<Object> tagList = (List<Object>) getValue(nbtTag);
            int n = 0;
            for (Object nbtBase : tagList) {
                if (BukkitUtils.getBukkitVersionNumber() < 11200) {
                    sj.add(n + ":" + toJsonStringForRawMessage(nbtBase));
                } else {
                    sj.add(toJsonStringForRawMessage(nbtBase));
                }
                n++;
            }
            return sj.toString();
        } else if (NBTTypeEnum.NBTTagString.isInstance(nbtTag)) {
            String value = (String) getValue(nbtTag);
            value = value.replace("\"", "\\\"");
            if (StringUtils.isNotBlank(value) && value.endsWith("\\")) {
                value = value + " ";
            }
            return "\"" + value + "\"";
        } else {
            return nbtTag.toString();
        }
    }

    @Override
    public <T> void setNBTTagListType(T tagList, byte type) {
        NBTUtilsImpl_all.type.set(tagList, type);
    }

    @Override
    public <T> T createNBTTagString(String str) {
        return (T) new NBTTagString(str == null ? "" : str);
    }
}
